﻿using System;
using System.Xml;
using System.Text;
using System.Reflection;
using Microsoft.SharePoint;
using System.Collections.Generic;
using System.Collections.Specialized;
using Microsoft.SharePoint.Security;
using System.Security.Permissions;
using Microsoft.SharePoint.WebControls;

namespace Voodoo.SharePoint.ReadUnreadMarks
{
    public class ReadUnreadField : SPFieldMultiLineText
    {
        internal static Guid READ_UNREAD_FIELD_ID = new Guid("{C94FE373-EEFE-4116-A254-73B6FBF10B83}");
        internal static Guid READ_UNREAD_FILTER_FIELD_ID = new Guid("{63844040-69CF-429E-9F49-A4C49AD309FD}");
        #region Declarations

        private static StringDictionary _NewColumnProperties = new StringDictionary();  // there are some odd circumstances where the property editor
                                                                                        // is recreated withtout saving itself (probably due to postback
                                                                                        // so this is a global that we use to keep information about newly
                                                                                        // created columns
                                                                                        // the pattern for the soluciton to the apparent bug in SharePoint
                                                                                        // came from a thread :
                                                                                        //http://jack.whyyoung.com/blog/www-sharepointblogs-com-MainFeed-aspx-GroupID-3/The-solution-to-saving-properties-i.htm
        internal const string NO_CONTEXT = "NO*SP*CONTEXT";                            
        internal const string COLUMN_RENDER_MODE_KEY = "ColumnRenderMode";
        internal const string CLEAR_READ_MARKS_ON_UPDATE_KEY = "ClearReadMarksOnUpdate";
        internal const string VISIBLE_ON_FORM_KEY = "VisibleOnForm";
        internal const string SHOW_INLINE_EDITING_TOOLS_KEY = "ShowInlineEditingTools";
        internal const string READ_IMAGE_URL_KEY = "ReadImageURL";
        internal const string UNREAD_IMAGE_URL_KEY = "UnreadImageURL";
        internal const string UNREAD_HTML_COLOR_KEY = "UnreadHTMLColor";
        internal const string ICON_DISPLAY = "Iconic";
        internal const string BOLD_DISPLAY = "BoldDisplay";
        internal const string UNREAD_COLOR = "";

        public const string READ_MARK_DELIMITER = ".";

        private string _ContextID = NO_CONTEXT;
        private string _ColumnRenderMode = string.Empty;
        private string _ReadImageUrl = string.Empty;
        private string _UnreadImageUrl = string.Empty;
        private string _UnreadHTMLColor = string.Empty;
        private bool _ClearReadMarksOnUpdate = false;
        private bool _ShowInlineEditingTools = false;
        private bool _VisibleOnForm = false;
        #endregion

        #region Constructors
        //this constructor is called with the internal name of the field
        public ReadUnreadField(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
            try
            {
                _ContextID = SPContext.Current.GetHashCode().ToString();
            }
            catch
            {
                _ContextID = NO_CONTEXT;
            }
            ReadCustomProperties();
        }

        //new field constructor we need to initialize the display name
        public ReadUnreadField(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
            try
            {
                _ContextID = SPContext.Current.GetHashCode().ToString();
            }
            catch
            {
                _ContextID = NO_CONTEXT;
            }
        }

 
        #endregion

        #region SPField imlementation
        public override Type FieldValueType
        {
            get
            {
                return typeof(Voodoo.SharePoint.ReadUnreadMarks.ReadUnreadField);
            }
        }

        public override BaseFieldControl FieldRenderingControl
        {
            [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
            get
            {
                BaseFieldControl fieldControl = new ReadUnreadFieldControl(this);
                fieldControl.FieldName = this.InternalName;
                return fieldControl;
            }
        }

        public override Microsoft.SharePoint.MobileControls.SPMobileBaseFieldControl FieldRenderingMobileControl
        {
            [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
            get
            {
                Microsoft.SharePoint.MobileControls.SPMobileBaseFieldControl mobileControl = new ReadUnreadMobileFieldControl(this);
                mobileControl.FieldName = this.InternalName;
                return mobileControl;
            }
        }

        public override string GetValidatedString(object value)
        {
            string returnValue = string.Empty;
            if (value is ReadUnreadFieldValue)
            {
                ReadUnreadFieldValue castValue = (ReadUnreadFieldValue)value;
                returnValue = castValue.ToString();
            }
            else
            {
                if (value is string)
                {
                    returnValue = (string)value;
                }
            }
            return returnValue;
        }

        public override object GetFieldValue(string value)
        {
            ReadUnreadFieldValue returnValue = new ReadUnreadFieldValue(value);
            return returnValue;
        }

        public override string GetFieldValueAsText(object value)
        {
            ReadUnreadFieldValue returnValue = new ReadUnreadFieldValue(value);
            return returnValue.ToString();
        }

        public override string GetFieldValueAsHtml(object value)
        {
            ReadUnreadFieldValue returnValue = new ReadUnreadFieldValue(value);
            return returnValue.ToHTMLString();
        }

        public override bool Filterable
        {
            get
            {
                return false;
            }
        }

        public override bool Sortable
        {
            get
            {
                return false;
            }
        }

        public override bool FilterableNoRecurrence
        {
            get
            {
                return false;
            }
        }

        public override object PreviewValueTyped
        {
            get
            {
                return null;
            }
        }


        
        public override void OnAdded(SPAddFieldOptions op)
        {
            base.OnAdded(op);
            Update();
            //HACK  we can only have one field because of the
            //      problems with CAML and XSL naming in 2010 Beta 2
            //      so delete any other fields
            List<ReadUnreadField> allFields = UtilityMethods.FindFieldsOnList(this.ParentList);
            for (int fieldIndex = 1; fieldIndex < allFields.Count; fieldIndex++)
            {
                try
                {
                    allFields[fieldIndex].Delete();
                }
                catch { }
            }
        }

        public override void OnDeleting()
        {
            VerifyEventHandler();
            base.OnDeleting();
        }

        public override void Update()
        {
            WriteCustomProperties();
             base.Update();
        }

        public override string DefaultValue
        {
            get
            {
                string returnValue = base.DefaultValue;
                if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
                {
                    SPUser currentUser = UtilityMethods.EnsureUser(System.Web.HttpContext.Current.User.Identity.Name);
                    returnValue = READ_MARK_DELIMITER + currentUser.ID.ToString() + READ_MARK_DELIMITER;
                }
                return returnValue;
            }
            set
            {
                base.DefaultValue = value;
            }
        }

        public override object DefaultValueTyped
        {
            get
            {
                return new ReadUnreadFieldValue(this.DefaultValue);

            }
        }

        public override bool NoCrawl
        {
            get
            {
                return true;
            }
            set
            {
                
            }
        }

        #endregion

        #region Property management
        private void ReadCustomProperties()
        {
            _ShowInlineEditingTools = bool.Parse(WrappedGetCustomProperty(SHOW_INLINE_EDITING_TOOLS_KEY, false.ToString()));
            _VisibleOnForm = bool.Parse(WrappedGetCustomProperty(VISIBLE_ON_FORM_KEY, false.ToString()));
            _ClearReadMarksOnUpdate = bool.Parse(WrappedGetCustomProperty(CLEAR_READ_MARKS_ON_UPDATE_KEY, false.ToString()));
            _ColumnRenderMode = WrappedGetCustomProperty(COLUMN_RENDER_MODE_KEY, string.Empty);
            _UnreadImageUrl = WrappedGetCustomProperty(UNREAD_IMAGE_URL_KEY, string.Empty);
            _ReadImageUrl = WrappedGetCustomProperty(READ_IMAGE_URL_KEY, string.Empty);
            _UnreadHTMLColor  = WrappedGetCustomProperty(UNREAD_HTML_COLOR_KEY, string.Empty);
        }

        private void WriteCustomProperties()
        {
            this.SetCustomProperty(COLUMN_RENDER_MODE_KEY, this.ColumnRenderMode);
            this.SetCustomProperty(CLEAR_READ_MARKS_ON_UPDATE_KEY, this.ClearReadMarksOnUpdate.ToString());
            this.SetCustomProperty(VISIBLE_ON_FORM_KEY, this.VisibleOnForm.ToString());
            this.SetCustomProperty(SHOW_INLINE_EDITING_TOOLS_KEY, this.ShowInlineEditingTools.ToString());
            this.SetCustomProperty(READ_IMAGE_URL_KEY, this.ReadImageUrl);
            this.SetCustomProperty(UNREAD_IMAGE_URL_KEY, this.UnreadImageUrl);
            this.SetCustomProperty(UNREAD_HTML_COLOR_KEY, this.UnreadHTMLColor);

            //HACK -- remove when SharePoint RTM simmilar problem exists in 
            //        CAML attribute propagation in SharePoint 3.0 but 
            //        neither works here so we steal the Direction field
            //        to process in the XSL
            string tempDescription = this.ColumnRenderMode + "#";
            tempDescription += this.ShowInlineEditingTools.ToString() + "#";
            tempDescription += this.UnreadHTMLColor + "#";
            tempDescription += this.ReadImageUrl + "#";
            tempDescription += this.UnreadImageUrl;
            base.Direction = tempDescription;

            //HACK -- Apparently CAML cant access custom properties in a view and SharePoint not
            //        push custom properties to Field attributes , so we will do it by manually
            //        updating the schema xml for our properties 
            XmlDocument xmlSchema = new XmlDocument();
            xmlSchema.LoadXml(this.SchemaXml);
            XmlNode fieldNode = xmlSchema.SelectSingleNode("/Field");
            if (null != fieldNode)
            {
                //Add our attribute to the field XML
                XmlAttribute crmAttribute = fieldNode.Attributes[COLUMN_RENDER_MODE_KEY];
                if (null == crmAttribute)
                {
                    crmAttribute = xmlSchema.CreateAttribute(COLUMN_RENDER_MODE_KEY);
                    fieldNode.Attributes.SetNamedItem(crmAttribute);
                }
                crmAttribute.InnerText = this.ColumnRenderMode;
                

                XmlAttribute crmuAttribute = fieldNode.Attributes[CLEAR_READ_MARKS_ON_UPDATE_KEY];
                if (null == crmuAttribute)
                {
                    crmuAttribute = xmlSchema.CreateAttribute(CLEAR_READ_MARKS_ON_UPDATE_KEY);
                    fieldNode.Attributes.SetNamedItem(crmuAttribute);
                }
                crmuAttribute.InnerText = this.ClearReadMarksOnUpdate.ToString().ToUpper();

                XmlAttribute rdAttribute = fieldNode.Attributes[SHOW_INLINE_EDITING_TOOLS_KEY];
                if (null == rdAttribute)
                {
                    rdAttribute = xmlSchema.CreateAttribute(SHOW_INLINE_EDITING_TOOLS_KEY);
                    fieldNode.Attributes.SetNamedItem(rdAttribute);
                }
                rdAttribute.InnerText = this.ShowInlineEditingTools.ToString().ToUpper();
 
                XmlAttribute vofAttribute = fieldNode.Attributes[VISIBLE_ON_FORM_KEY];
                if (null == vofAttribute)
                {
                    vofAttribute = xmlSchema.CreateAttribute(VISIBLE_ON_FORM_KEY);
                    fieldNode.Attributes.SetNamedItem(vofAttribute);
                }
                vofAttribute.InnerText = this.VisibleOnForm.ToString().ToUpper();
 
                XmlAttribute rimgAttribute = fieldNode.Attributes[READ_IMAGE_URL_KEY];
                if (null == rimgAttribute)
                {
                    rimgAttribute = xmlSchema.CreateAttribute(READ_IMAGE_URL_KEY);
                    fieldNode.Attributes.SetNamedItem(rimgAttribute);
                }
                rimgAttribute.InnerText = this.ReadImageUrl;

                XmlAttribute urimgAttribute = fieldNode.Attributes[UNREAD_IMAGE_URL_KEY];
                if (null == urimgAttribute)
                {
                    urimgAttribute = xmlSchema.CreateAttribute(UNREAD_IMAGE_URL_KEY);
                    fieldNode.Attributes.SetNamedItem(urimgAttribute);
                }
                urimgAttribute.InnerText = this.UnreadImageUrl;

                XmlAttribute urcolorAttribute = fieldNode.Attributes[UNREAD_HTML_COLOR_KEY];
                if (null == urcolorAttribute)
                {
                    urcolorAttribute = xmlSchema.CreateAttribute(UNREAD_HTML_COLOR_KEY);
                    fieldNode.Attributes.SetNamedItem(urcolorAttribute);
                }
                urcolorAttribute.InnerText = this.UnreadHTMLColor;
            }

            //Ok now reset it, after we do this dont monkey with the 
            //properties until its been updated
            this.SchemaXml = fieldNode.OuterXml;

            //Clear the new column property flags
            lock (_NewColumnProperties)
            {
                string contextKey = _ContextID + CLEAR_READ_MARKS_ON_UPDATE_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + COLUMN_RENDER_MODE_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + SHOW_INLINE_EDITING_TOOLS_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + VISIBLE_ON_FORM_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + UNREAD_IMAGE_URL_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + READ_IMAGE_URL_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + UNREAD_HTML_COLOR_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

            }
           
            VerifyEventHandler();

        }

  
        private void VerifyEventHandler()
        {
            if (null != this.ParentList)
            {
                //First remove all of our event handlers
                UninstallAllEventHandlers();
                bool clearReadMarks = false;
                //Now check to see if any fields need it for clearing read marks
                foreach (SPField readUnreadField in this.ParentList.Fields)
                {
                    if (typeof(ReadUnreadField).Name == readUnreadField.TypeAsString)
                    {
                        //we are not fully saved yet so we cant use a cast
                        //to figure this out, but luckily we updated the SchemaXml
                        XmlDocument xmlSchema = new XmlDocument();
                        xmlSchema.LoadXml(readUnreadField.SchemaXml);
                        XmlNode fieldNode = xmlSchema.SelectSingleNode("/Field");
                        if (null != fieldNode)
                        {
                            if (!clearReadMarks)
                            {
                                XmlAttribute crmuAttribute = fieldNode.Attributes[CLEAR_READ_MARKS_ON_UPDATE_KEY];
                                if (null != crmuAttribute)
                                {
                                    clearReadMarks = bool.Parse(crmuAttribute.InnerText);
                                }
                            }
                        }
                        if (clearReadMarks) break;
                    }

                }
                InstallEventHandlers(clearReadMarks);
            
            }
        }

        private void InstallEventHandlers(bool installUpdatedHandler)
        {
            if (null != this.ParentList)
            {
                //Install our ReadUnreadListItemEventHandler class for ItemUpdated 
                string eventAssembly = Assembly.GetExecutingAssembly().FullName;
                string eventClass = typeof(ReadUnreadListItemEventHandler).ToString();
                if (installUpdatedHandler)
                {
                    this.ParentList.EventReceivers.Add(SPEventReceiverType.ItemUpdated, eventAssembly, eventClass);
                }
                this.ParentList.EventReceivers.Add(SPEventReceiverType.ItemAdded, eventAssembly, eventClass);
                this.ParentList.Update();
            }
        }

        private void UninstallAllEventHandlers()
        {
            if (null != this.ParentList)
            {
                //Remove all registered copies of our ReadUnreadListItemEventHandler 
                string eventAssembly = Assembly.GetExecutingAssembly().FullName;
                string eventClass = typeof(ReadUnreadListItemEventHandler).ToString();
                bool updateList = false;
                for (int eventIndex = this.ParentList.EventReceivers.Count; eventIndex > 0; eventIndex--)
                {
                    SPEventReceiverDefinition eventDefinition = this.ParentList.EventReceivers[eventIndex - 1];
                    if (eventDefinition.Assembly == eventAssembly)
                    {
                        if (eventDefinition.Class == eventClass)
                        {
                            eventDefinition.Delete();
                            updateList = true;
                        }
                    }

                }
                if (updateList)
                {
                    this.ParentList.Update();
                }
            }
        }

        private string WrappedGetCustomProperty(string propertyName, string defaultValue)
        {
            //Since we have the static collection of pending updates
            //we need to check it first , if its not there we will
            //try to read it as a custom property
            string returnValue = string.Empty;
            lock (_NewColumnProperties)
            {
                string contextKey = _ContextID + propertyName;
                if (_NewColumnProperties.ContainsKey(contextKey))
                {
                    returnValue = _NewColumnProperties[contextKey];
                }
            }
            if (string.IsNullOrEmpty(returnValue))
            {
                try
                {
                    object sharePointCastValue = this.GetCustomProperty(propertyName);
                    returnValue = this.GetCustomProperty(propertyName) as string;
                    if (string.IsNullOrEmpty(returnValue))
                    {
                        if (null != sharePointCastValue)
                        {
                            returnValue = sharePointCastValue.ToString();
                        }
                    }
                }
                catch
                {
                    returnValue = defaultValue;
                }
            }
            if (string.IsNullOrEmpty(returnValue)) returnValue = defaultValue;
            return returnValue;
        }

        public void SetNewColumnPropertyCacheValue(string propertyKey, string propertyValue)
        {
            //Add a new string value to the static new property cache
            lock (_NewColumnProperties)
            {
                string contextKey = _ContextID + propertyKey;
                _NewColumnProperties[contextKey] = propertyValue;
            }
        }
        #endregion

        #region Public properties

        public bool VisibleOnForm
        {
            //Class property, if not set then the
            //form javascript for "hiding" the label 
            //will be emmited on New and Display forms
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + VISIBLE_ON_FORM_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _VisibleOnForm;
                    }
                }
            }
            set
            {
                _VisibleOnForm = value;
            }
        }

        public bool ClearReadMarksOnUpdate
        {
            //Class property, if set then install the 
            //event handler for clearing read marks
            //when an item is updated
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + CLEAR_READ_MARKS_ON_UPDATE_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _ClearReadMarksOnUpdate;
                    }
                }
            }
            set
            {
                _ClearReadMarksOnUpdate = value;
            }
        }

        public bool ShowInlineEditingTools
        {
            //Class property, if  set then the column
            //also emits the raw data , can get quite large
            //but sometimes necessary for debugging
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + SHOW_INLINE_EDITING_TOOLS_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _ShowInlineEditingTools;
                    }
                }
            }
            set
            {
                _ShowInlineEditingTools = value;
            }
        }

         public string UnreadHTMLColor
        {
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + UNREAD_HTML_COLOR_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _UnreadHTMLColor;
                    }
                }
            }
            set
            {
                _UnreadHTMLColor = value;
            }
        }

        public string UnreadImageUrl
        {
            //Class property, the image URL for an unread icon
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + UNREAD_IMAGE_URL_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _UnreadImageUrl;
                    }
                }
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "/_layouts/images/Voodoo.ListItemReadUnreadSiteColumn/clscUnreadMark.gif";
                }
                //If you just type an image name then prepend the layouts 
                if (!value.StartsWith("/") && !value.StartsWith("http"))
                {
                    string[] valueParts = value.Split('/');
                    if (valueParts.Length < 2)
                    {
                        value = "/_layouts/images/" + value;
                    }
                }
                _UnreadImageUrl = value;
            }
        }
        
        public string ReadImageUrl
        {
            //Class property, the image URL for a read icon
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + READ_IMAGE_URL_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _ReadImageUrl;
                    }
                }
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "/_layouts/images/Voodoo.ListItemReadUnreadSiteColumn/clscReadMark.gif";
                }
                //If you just type an image name then prepend the layouts 
                if (!value.StartsWith("/") && !value.StartsWith("http"))
                {
                    string[] valueParts = value.Split('/');
                    if (valueParts.Length < 2)
                    {
                        value = "/_layouts/images/" + value;
                    }
                }
                _ReadImageUrl = value;
            }
        }

        public string ColumnRenderMode
        {
            //Class property, set to Iconic or BoldDisplay
            // which are simple images or javascript to highlight the row.
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + COLUMN_RENDER_MODE_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _ColumnRenderMode;
                    }
                }
            }
            set
            {
                _ColumnRenderMode = value;
            }
        }
        #endregion

    }
}
